﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;

namespace Asol.Reporting.Support.Components
{
    /// <summary>
    /// Combo s našeptávačem, z něhož se vybírá filtrováním
    /// </summary>
    public class ComboFilterBox : ComboBox
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        public ComboFilterBox()
        {
            this.DropDownStyle = ComboBoxStyle.DropDown;
            this.TextChanged += new EventHandler(_TextChanged);
        }
        #region Našeptávač
        private void _TextChanged(object sender, EventArgs e)
        {
            this._PrepareItemsFromWhisper();
        }
        private void _PrepareItemsFromWhisper()
        {
            // if (this.DroppedDown) return;
            if (this._TextChangedInternal) return;       // Právě se provádí změny
            if (this._WhisperList == null) return;       // Nejsou položky do nabídky

            string text = this.Text;
            string find = text.Trim();
            int length = find.Length;
            if (length > 0 && this.SelectedIndex >= 0) return;         // Uživatel má zadaný text, a listuje položkami v nabídce

            this._WhisperListApply(true);
        }
        /// <summary>
        /// Převezme aktuální data a naplní nabídku this.Items
        /// </summary>
        private void _WhisperListApply(bool withDroppedDown)
        {
            if (this._WhisperList == null) return;

            int min = this.MinimumTextLengthForWhisperFill;
            string text = this.Text;
            string find = text.Trim();
            int length = find.Length;
            if (length > 0 && length < min) return;

            try
            {
                this._TextChangedInternal = true;

                if (min > 0 && length < min)
                {
                    if (length == 0)
                    {
                        if (withDroppedDown && this.DroppedDown)
                            this.DroppedDown = false;
                        this.Items.Clear();
                    }
                    return;
                }
                int selStart = this.SelectionStart;
                int selLength = this.SelectionLength;

                if (length > 0)
                {   // Když je podle čeho filtrovat (uživatel něco zadal):
                    List<object> exactList = this._WhisperList.Where(w => _IsExact(w, text)).ToList();
                    List<object> otherList = this._WhisperList.Where(w => _IsOther(w, text)).ToList();

                    if (exactList.Count > 1) exactList.Sort(_CompareSort);
                    if (otherList.Count > 1) otherList.Sort(_CompareSort);

                    this._FillItemsFrom(withDroppedDown, withDroppedDown, find, exactList, otherList);
                }
                else
                {   // Není podle čeho filtrovat, ale nabídku máme plnit i pro prázdný text:
                    List<object> wholeList = this._WhisperList.ToList();
                    int cnt = wholeList.Count;
                    if (wholeList.Count > 1) wholeList.Sort(_CompareSort);

                    this._FillItemsFrom(withDroppedDown, false, find, wholeList, null);
                }

                this.SelectedIndex = -1;
                this.Text = text;
                this.SelectionStart = selStart;
                this.SelectionLength = selLength;
            }
            finally
            {
                this._TextChangedInternal = false;
            }
        }
        private static int _CompareSort(object a, object b)
        {
            if (a == null && b == null) return 0;
            if (a == null) return -1;
            if (b == null) return 1;
            return String.Compare(a.ToString(), b.ToString(), StringComparison.CurrentCultureIgnoreCase);
        }
        private void _FillItemsFrom(bool listHide, bool listShow, string text, IEnumerable<object> list1, IEnumerable<object> list2)
        {
            List<object> list = new List<object>();
            if (list1 != null) list.AddRange(list1);
            if (list2 != null) list.AddRange(list2);
            int index = (!String.IsNullOrEmpty(text) ? list.FindIndex(x => String.Equals(x.ToString(), text, StringComparison.CurrentCultureIgnoreCase)) : -1);
            int count = list.Count;

            if (listHide && count == 0 && this.DroppedDown)
                this.DroppedDown = false;
            
            this.Items.Clear();
            this.Items.AddRange(list.ToArray());
            if (index >= 0)
                this.SelectedIndex = index;

            if (listShow && count > 0 && count <= this.MaximumComboItemsForDropDown && !this.DroppedDown)
                this.DroppedDown = true;
        }
        private bool _TextChangedInternal;
        private bool _IsExact(object whisper, string text)
        {
            return whisper.ToString().StartsWith(text, StringComparison.CurrentCultureIgnoreCase);
        }
        private bool _IsOther(object whisper, string text)
        {
            return (whisper.ToString().IndexOf(text, StringComparison.CurrentCultureIgnoreCase) > 0);
        }
        #endregion
        #region Public property
        /// <summary>
        /// Aktuálně vybraný objekt v našeptávači. Get vrací this.SelectedItem.
        /// Set zajistí, že dodaný objekt bude přítomen v seznamu WhisperList i v seznamu Items.
        /// </summary>
        public object WhisperSelectedItem
        {
            get { return this.SelectedItem; }
            set
            {
                object item = null;
                if (value != null)
                {
                    int idx;
                    if (this._WhisperList != null)
                    {
                        idx = this._WhisperList.IndexOf(value);
                        if (idx < 0)
                            this._WhisperList.Add(value);
                    }
                    idx = this.Items.IndexOf(value);
                    if (idx < 0)
                        this.Items.Add(value);
                    item = value;
                }
                this.SelectedItem = item;
                
                this.Text = (item == null ? "" : item.ToString());
                this.Select(0, 0);
                // this.SelectionStart = 0;
                // this.SelectionLength = 0;
            }
        }
        /// <summary>
        /// Soupis textových hodnot pro našeptávač
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IEnumerable<object> WhisperList
        {
            get { return this._WhisperList; }
            set
            {
                this._WhisperList = (value == null ? null : new List<object>(value));
                this._WhisperListApply(false);
            }
        }
        private List<object> _WhisperList;
        /// <summary>
        /// Pokud má našeptávací seznam málo položek (nejvýše tuto hodnotu), pak bude do nabídky comboboxu naplněn celý při iniciaci. Uživatel pak může rozbalit combo i při prázdném zadání.
        /// </summary>
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Category("Reporting")]
        [Description("Pokud má našeptávací seznam málo položek (nejvýše tuto hodnotu), pak bude do nabídky comboboxu naplněn celý při iniciaci. Uživatel pak může rozbalit combo i při prázdném zadání.")]
        [DefaultValue(20)]
        public int WholeWhisperListToCombo
        {
            get { return this._AllItemsFillToCount; }
            set
            {
                this._AllItemsFillToCount = value;
                this._WhisperListApply(false);
            }
        }
        private int _AllItemsFillToCount = 20;
        /// <summary>
        /// Kolik nejméně znaků musí zadat uživatel, aby došlo k filtrování z WhisperList do nabídky Combo. Hodnota 0 = nabídka bude naplněna i pro prázdný text.
        /// </summary>
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Category("Reporting")]
        [Description("Kolik nejméně znaků musí zadat uživatel, aby došlo k filtrování z WhisperList do nabídky Combo. Hodnota 0 = nabídka bude naplněna i pro prázdný text.")]
        [DefaultValue(2)]
        public int MinimumTextLengthForWhisperFill
        {
            get { return this._MinimumTextLengthToWhisperFill; }
            set
            {
                this._MinimumTextLengthToWhisperFill = value;
                this._WhisperListApply(false);
            }
        }
        private int _MinimumTextLengthToWhisperFill = 2;
        /// <summary>
        /// Kolik položek může mít nabídka, která bude automaticky otevřena. Pokud bude filtru vyhovovat více položek, nabídka se neotevře (byla by nepřehledná).
        /// </summary>
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Category("Reporting")]
        [Description("Kolik položek může mít nabídka, která bude automaticky otevřena. Pokud bude filtru vyhovovat více položek, nabídka se neotevře (byla by nepřehledná).")]
        [DefaultValue(50)]
        public int MaximumComboItemsForDropDown
        {
            get { return this._MaximumComboItemsForDropDown; }
            set
            {
                this._MaximumComboItemsForDropDown = value;
                this._WhisperListApply(false);
            }
        }
        private int _MaximumComboItemsForDropDown = 50;
        public void Clear()
        {
            this.Items.Clear();
            this._WhisperList = null;
            this.Text = "";
        }
        #endregion
    }
}
